<?php 
/**
 * This file is part of DomMvc.
 * 
 * DomMvc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomMvc is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomMvc.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
 * This library is not free software. You can not redistribute it and/or modify it in anyway!
 * 
* @package DomMvc
* 
* This package depends on Smarty.
* Needed constants:
* DOM_TEMPLATES_DIR
* DOM_TEMPLATES_URL
* DOM_MEDIA_DIR
* DOM_MEDIA_URL
* DOM_CONTROLLERS_DIR
* DOM_MODELS_DIR
* SMARTY_COMPILE_DIR - DomView sets smarty compile dir to this value
* SMARTY_CACHE_DIR - DomView sets smarty cache dir to this value
* DOM_PARTITIONERS_MOD_CHECK - checks if any changes are made to partitioner files. Useful for development, should be set to false for publishing
*/
class DomSite {
	
	/**
	* @var String
	* Controllers group is used to group alike controllers together. It is actually just folder in controllers/, view/ and media/ folders.
	*/
	private $controllersGroup;
	
	/**
	* @var Array
	* Already loaded controllers. Doesn\'t allow creating multiple instances of controllers with same name.
	*/
	private $controllers = array();
	
	/**
	* @var DomController Selected controller. This has always DomController object in it. If nothing selected, then this is default controller.
	*/
	private $selectedController;
	
	/**
	* @var String this page url
	*/
	private $pageUrl;
	/**
	 * @var String current url
	 */
	private $currentUrl;
	
	/**
	* @var DomView Base view renderer
	*/
	private $view;
	
	/**
	* @var String Index controller name.
	*/
	private $indexController = 'Index';
	
	/**
	* @var Array
	*/
	private $rewriteRules = array();
	
	/**
	* @var DomSiteLocation
	*/
	private $location;
	
	/**
	* Logged in user
	* @param DomUser
	*/ 
	private $user;
	/**
	 * Set to true, if URL is analyzed
	 * @var bool
	 */
	private $urlAnalyzed = false;
	
	/**
	* @param String $pageUrl
	* @param String $controllersGroup
	*/
	final public function __construct( $pageUrl = '', $controllersGroup = '' ) {
		
		//$this->redirectIfWww();
		
		$this->controllersGroup = $controllersGroup;
		$this->pageUrl = $pageUrl;
		
		$this->view = new DomView();
		$this->view->setTemplate('index.html');
		
		global $SITE;
		if( isset($SITE) === true ) throw new RuntimeException('global variable $SITE is set!');
		$SITE = $this;
		
		$this->init();
	}
	
	/**
	* This method is called after object is constructed.
	* Can do some initializing - Database connection maybe.
	*/
	public function init() {
		
	}
	
	/**
	* Set rewriting rules for using custom url controlling.
	*
	* Usually url is like www.site.com/CONTROLLER_NAME/METHOD_NAME/ARGS
	* Rewriting rules can be used to make urls www.site.com/CUSTOM/ARGS that will direct to specified controller and method .
	*
	* Sample:
	* <code>
	* array(
	*	'custom' => array('CONTROLLER_NAME','METHOD_NAME')
	* );
	* </code>
	* NB! All keys must be lowercase, or they will not work correctly!
	*
	* @param Array $rules
	*/
	public function setRewriteRules( array $rules ) {
		$this->rewriteRules = $rules;
	}
	/**
	 * If url is http://www.something then user will be redirected to http://something.
	 */
	public function redirectIfWww() {
		if( preg_match('/^www\.*/i', $_SERVER['HTTP_HOST'], $m ) > 0 ) {
			$host = substr($_SERVER['HTTP_HOST'],4);
			if( !empty($_SERVER['QUERY_STRING']) ) {
				$queryString = $_SERVER['QUERY_STRING'];
				if( substr($queryString, 0, 5) == 'args=' )
					$queryString = substr($queryString, 5);
				else	
					$queryString = '&'.$queryString;
			}
			$dir = dirname($_SERVER['SCRIPT_NAME']).'/';
			header('location: http://' . $host . $dir . $queryString );
		}
	}
	
	/**
	* @param String $name Index controller name
	*/
	final public function setIndexController( $name ) {
		$this->indexController = $name;
	}
	
	/**
	* Load Controller object
	* @param String $name
	* @param String $group group name or path to the controller file. Path must start with '/'
	* @return DomController
	*/
	final public function loadController( $name, $group = NULL ) {
		$sName = strtolower($name);
		if( isset($this->controllers[$sName]) === false ) {
			$path = $this->getControllersPath( $name, $group );
			if( is_file( $path ) === true ) {
				require_once($path);
				$className = $name.'Controller';
				$this->controllers[$sName] = new $className( $name );
			}
		}
		return $this->controllers[$sName];
	}	
	/**
	 * @param String $name
	 * @param String $group group name or path to the controller file. Path must start with '/'
	 * @return String
	 */
	public function getControllersPath( $name, $group = NULL ) {
		$path = '';
		if( $group[0] != '/' ) {
			$path = DOM_CONTROLLERS_DIR;
		}
		$path .= ($group === NULL ? $this->controllersGroup : $group) . '/' . ucfirst($name) . 'Controller.php';
		
		return $path;
	}

	/**
	* @return String
	*/
	final public function getUrl() {
		return $this->pageUrl;
	}	

	/**
	* @return String
	*/
	public function getTemplateUrl( $add = NULL ) {
		return DOM_TEMPLATES_URL . $this->controllersGroup . '/' . $add;
	}
	
	/**
	* @return String
	*/
	public function getMediaUrl( $add = NULL ) {
		return DOM_MEDIA_URL . $add;
	}
	
	/**
	* @return DomSiteLocation
	*/
	public function getLocation() {
		return $this->location;
	}
	/**
	 * @param String $add 
	 * @return String
	 */
	public function getCurrentUrl( $add = NULL ) {
		return $this->currentUrl . $add;
	}
	
	/**
	 * Analyze current URL, find out which method, controller to use.
	 */
	public function analyzeUrl() {
		
		$query = substr($_SERVER['QUERY_STRING'],0,4) == 'args' ? substr($_SERVER['QUERY_STRING'],5) : $_SERVER['QUERY_STRING'];
		$this->currentUrl = $this->pageUrl . $query;
		
		$badRequest = false;
		// remove ending slash.
		if( substr($_GET['args'],-1,1) == '/' ) {
			$_GET['args'] = substr($_GET['args'],0,-1);
		}	
		$args = explode('/', $_GET['args'] );
		unset($_GET['args']);
		
		if( count($args) > 0 ) {
			$controller = array_shift($args);
		}
		
		// rewrite URL
		$rule = $this->rewriteRules[strtolower($controller)];	
		if( $rule != NULL ) {
			$method = $rule[1];
			$controller = $rule[0];
		}

		if( $controller == '' or file_exists($this->getControllersPath( $controller )) === false ) {
			$controller = $this->indexController;
			$method = 'index';
		
		} else if( $method == '' ) {
			$method = array_shift( $args );
			if( $method == '' or substr($method,0,1) == '_' ) {
				$method = 'index';
			}
		}
		
		$this->location = new DomSiteLocation( $controller, $method, array_values($args) );
		
		if( DOM_MONITOR_LOG === true ) {
			DomMonitor::setLocation( $controller, $method );
		}
		
		$this->urlAnalyzed = true;
	}

	/**
	* @return String
	*/
	public function fetch() {
		
		if( $this->urlAnalyzed === false ) {
			$this->analyzeUrl();
		}
		
		// load selected controller
		$this->selectedController = $this->loadController( $this->location->controller );
		
		if( $this->selectedController === NULL ) {
			throw new RuntimeException('Can\'t load any controller. Needed controller <strong>'.$this->location->controller.'</strong> was not found!');
		}
		
		try {
			
			// setup
			$result = $this->selectedController->setup();
			if( $result === false )
				$this->loadController( $this->indexController )->setup();
			// ------
				
			// controller
			if( $badRequest === true or is_callable( array($this->selectedController, $this->location->method) ) === false ) {
				call_user_func( array($this->selectedController, 'error404'));
			} else {
				try {
					call_user_func_array( array($this->selectedController, $this->location->method), $this->location->args );
				
				// catch and find exception that says missing argument for controller method. If that's the case show error404 page.
				} catch( RuntimeException $e ) {
					$invalidArguments = strpos( $e->getMessage(),'Missing argument')>0 && stripos( $e->getMessage(),'for '.get_class($this->selectedController).'::'.$this->location->method)>0;
					$invalidMethodCall = strpos( $e->getMessage(),'First argument is expected to be a valid callback')>0;
					
					if( $invalidArguments or $invalidMethodCall) {
						$this->selectedController->error404();
					} else {
						throw $e;
					}
				}	
			}
			// ------
			
			// finalize
			$result = $this->selectedController->finalize();
			if( $result === false )
				$this->loadController( $this->indexController )->finalize();
			// ------
			
		} catch( NotAuthorizedException $e ) {
			call_user_func_array( array($this->selectedController, 'error403'), array($e->getCode(), $e->getMessage()));
		}
			
		return $this->view->fetch();
	}
	
	/**
	* Echoes fetched site.
	*/
	public function display() {
		echo $this->fetch();
	}
	
	/**
	* @return DomView
	*/
	final public function getView() {
		return $this->view;
	}
	
	final public function __get( $var ) {
		return $this->$var;
	}

	/**
	* Sets logged in user. This user rights will be used to determine what content user can see and what not.
	* @param DomUser $user
	*/
	public function setUser( DomUser $user = NULL ) {
		$this->user = $user;
	}
	
	/**
	* @return DomUser
	*/
	public function getUser() {
		return $this->user;
	}
	
}
?>